Willkommen zu einem weiteren aufregendem Tutorial! Dieses Mal konzentrieren wir uns eher auf den Effekt als auf die Grafiken, obwohl das finale Ergebnis wirklich cool aussieht! In diesem Tutorial werden Sie lernen wie man nahtlos von einem Objekt in ein anderes morpht. Ähnlich dem Effekt, den ich im Dolphin Demo verwendet habe. Obwohl es ein paar Haken gibt. Als erstes ist anzumerken, dass jedes Objekt die selbe Anzahl Punkte haben muss. Es muss schon ziemlicher Zufall sein, 3 Objekte zu haben, die exakt die selbe Anzahl an Vertices haben, aber wie es nun mal so ist, haben wir in diesem Tutorial 3 Objekte, mit genau der selben Anzahl an Punkten :) Verstehen Sie mich nicht falsch, Sie können Objekte mit verschiedener Anzahl verwenden, aber der Übergang von einem Objekt in ein anderes, sieht komisch aus und nicht so weich.
Sie werden ebenso lernen, wie man Objekt-Daten aus einer Datei liest. Ähnlich wie das Format, welches in Lektion 10 verwendet wurde, obwohl es nicht schwer sein sollte, den Code so zu modifizieren, dass .ASC Dateien oder andere Text basierte Daten-Dateien einzulesen. Es ist ein wirklich cooler Effekt, ein wirklich cooles Tutorial, lassen Sie uns also beginnen!
Wir fangen wie gewohnt an. Inkludieren alle benötigten Header Dateien, zusammen mit den Math und Standard Input / Output Headern. Beachten Sie, dass wir glaux nicht inkludieren. Das rührt daher, dass wir Punkte anstatt von Texturen in diesem Tutorial zeichnen. Nachdem Sie dieses Tutorial durch haben, können Sie versuchen etwas mit Polygonen, Linien und Texturen zu spielen!
#include < windows.h> // Header Datei für Windows #include < math.h> // Math Library Header Datei #include < stdio.h> // Header Datei für Standard Input/Output #include < gl\gl.h> // Header Datei für die OpenGL32 Library #include < gl\glu.h> // Header Datei für die GLu32 Library HDC hDC=NULL; // Device Context Handle HGLRC hRC=NULL; // Rendering Context Handle HWND hWnd=NULL; // Fenster Handle HINSTANCE hInstance; // Instanz Handle bool keys[256]; // Tasten Array bool active=TRUE; // Programm ist aktiv bool fullscreen=TRUE; // Fullscreen Flag ist standardmäßig auf TRUE gesetzt
GLfloat xrot,yrot,zrot, // X, Y & Z Rotation xspeed,yspeed,zspeed, // X, Y & Z Rotationsgeschwindigkeit cx,cy,cz=-15; // X, Y & Z Position int key=1; // wird verwendet um sicherzustellen, dass nicht die selbe Morph Taste gedrückt wurde int step=0,steps=200; // Step Counter und maximale Anzahl Schritten (Steps) bool morph=FALSE; // standardmäßig ist morph auf False gesetzt (es wird nicht gemorpht)
typedef struct // Struktur für 3D Punkte
{
float x, y, z; // X, Y & Z Punkte
} VERTEX; // namens VERTEX
typedef struct // Struktur für eine Objekt
{
int verts; // Anzahl der Vertices für das Objekt
VERTEX *points; // Ein Vertex (Vertex x,y & z)
} OBJECT; // namens OBJECT
int maxver; // wird eventuell die maximale Anzahl der Vertices enthalten OBJECT morph1,morph2,morph3,morph4, // unsere 4 morphbaren Objekte (morph1,2,3 & 4) helper,*sour,*dest; // Helper Objekt, Quell-Objekt, Ziel-Objekt
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // Deklaration
void objallocate(OBJECT *k,int n) // Alloziiere Speicher für jedes Objekt
{ // und definiere Punkte
k->points=(VERTEX*)malloc(sizeof(VERTEX)*n); // Setze Punkte gleich auf VERTEX * Anzahl der Vertices
} // (3 Punkte für jeden Vertex)
void objfree(OBJECT *k) // gibt das Objekt frei (und den Speicher)
{
free(k->points); // gibt Zeiger frei
}
void readstr(FILE *f,char *string) // Liest einen String aus einer Datei (f)
{
do // mache folgendes
{
fgets(string, 255, f); // hole einen String mit maximalen 255 Zeichen aus f (Datei)
} while ((string[0] == '/') || (string[0] == '\n')); // bis das Ende der Zeile erreicht wurde
return; // kehre zurück
}
void objload(char *name,OBJECT *k) // Lädt Objekt aus Datei (name)
{
int ver; // wird die Vertice Anzahl enthalten
float rx,ry,rz; // enthält Vertex X, Y & Z Position
FILE *filein; // zu öffnender Dateiname
char oneline[255]; // enthält eine Textzeile (255 Zeichen maximal)
filein = fopen(name, "rt"); // öffnet die Datei zum lesen
// STRG Z symbolisiert das Ende der Datei
readstr(filein,oneline); // springt zum Code, der eine Textzeile aus der Datei einliest
sscanf(oneline, "Vertices: %d\n", &ver); // durchsuche Text auf "Vertices: ". Zahl wird danach in ver gespeichert
k->verts=ver; // Setze Objekt verts Variable gleich auf den Wert von ver
objallocate(k,ver); // springt zum Code, der den Speicher alloziiert, um das Objekt zu speichern
for (int i=0;i< ver;i++) // iteriert durch die Vertices
{
readstr(filein,oneline); // liest die nächste Textzeile ein
sscanf(oneline, "%f %f %f", &rx, &ry, &rz); // sucht nach 3 Fließkommazahlen, die in rx,ry & rz gespeichert werden
k->points[i].x = rx; // Setze Objekt (k) points.x Wert auf rx k->points[i].y = ry; // Setze Objekt (k) points.y Wert auf ry k->points[i].z = rz; // Setze Objekt (k) points.z Wert auf rz } fclose(filein); // schließe die Datei if(ver>maxver) maxver=ver; // wenn ver größer als maxver ist, dann setze maxver auf ver } // damit verfolgen wir die höchste verwendete Anzahl an Vertices
VERTEX calculate(int i) // berechne Bewegung der Punkte während des Morphings
{
VERTEX a; // Temporärer Vertex namens a
a.x=(sour->points[i].x - dest->points[i].x) / steps; // a.x Wert gleich Quell x - Ziel x Dividiert durch Steps
a.y=(sour->points[i].y - dest->points[i].y) / steps; // a.y Wert gleich Quell y - Ziel y Dividiert durch Steps
a.z=(sour->points[i].z - dest->points[i].z) / steps; // a.z Wert gleich Quell z - Ziel z Dividiert durch Steps
return a; // gebe die Ergebnisse zurück
}
GLvoid ReSizeGLScene(GLsizei width, GLsizei height) // verändert die Größe und initialisiert das GL-Fenster
int InitGL(GLvoid) // Der ganze Setup Kram für OpenGL kommt hier rein
{
glBlendFunc(GL_SRC_ALPHA,GL_ONE); // Setze die Blending Funktion für Translucency
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // dies setzt die Hintergrundfarbe auf schwarz
glClearDepth(1.0); // aktiviert die Löschung des Depth Buffers
glDepthFunc(GL_LESS); // Die Art des auszuführenden Depth Test
glEnable(GL_DEPTH_TEST); // aktiviert Depth Testing
glShadeModel(GL_SMOOTH); // aktiviert Smooth Color Shading
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // wirklich nette Perspektiven Berechnungen
maxver=0; // Setze maximale Vertices standardmäßig auf 0
objload("data/sphere.txt",&morph1); // Lade das erste Objekt nach morph1 aus der Datei sphere.txt
objload("data/torus.txt",&morph2); // Lade das zweite Objekt nach morph2 aus der Datei torus.txt
objload("data/tube.txt",&morph3); // Lade das dritte Objekt nach morph3 aus der Datei tube.txt
objallocate(&morph4,486); // Manuelle Speicherresevierung für ein 4tes 468 Vertices Objekt (morph4)
for(int i=0;i<486;i++) // iteriere durch alle 468 Vertices
{
morph4.points[i].x=((float)(rand()%14000)/1000)-7; // morph4 x Punkt wird ein zufälliger Wert zwischen -7 und 7 zugewiesen
morph4.points[i].y=((float)(rand()%14000)/1000)-7; // morph4 y Punkt wird ein zufälliger Wert zwischen -7 und 7 zugewiesen
morph4.points[i].z=((float)(rand()%14000)/1000)-7; // morph4 z Punkt wird ein zufälliger Wert zwischen -7 und 7 zugewiesen
}
objload("data/sphere.txt",&helper); // Lade sphere.txt Object nach Helper (wird als Startpunkt verwendet)
sour=dest=&morph1; // Quell & Ziel werden auf das erste Objekt (morph1) gesetzt
return TRUE; // Initialisierung verlief OK
}
void DrawGLScene(GLvoid) // Hier kommt der ganze Zeichnen-Kram hin
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Lösche den Bildschirm und den Depth-Buffer
glLoadIdentity(); // Resette die aktuelle Modelview Matrix
glTranslatef(cx,cy,cz); // Translatiere die aktuelle Position um mit dem Zeichnen zu beginnen
glRotatef(xrot,1,0,0); // Rotatiere auf der X-Achse um xrot
glRotatef(yrot,0,1,0); // Rotatiere auf der Y-Achse um yrot
glRotatef(zrot,0,0,1); // Rotatiere auf der Z-Achse um zrot
xrot+=xspeed; yrot+=yspeed; zrot+=zspeed; // erhöhe xrot,yrot & zrot um xspeed, yspeed & zspeed
GLfloat tx,ty,tz; // Temporäre X, Y & Z Variablen
VERTEX q; // enthält zurückgelieferte berechnete Werte für ein Vertex
glBegin(GL_POINTS); // fange an Punkte zu zeichnen
for(int i=0;i< morph1.verts;i++) // iteriere durch alle Verts von morph1 (Alle Objekte haben
{ // die selbe Anzahl an Verts, nur der Einfachkeit halber, Sie könnten auch maxver verwenden)
if(morph) q=calculate(i); else q.x=q.y=q.z=0; // wenn morph gleich True ist, berechne Bewegung ansonsten Movement=0
helper.points[i].x-=q.x; // Subtrahiere q.x Einheiten von helper.points[i].x (Bewegung auf der X Achse)
helper.points[i].y-=q.y; // Subtrahiere q.y Einheiten von helper.points[i].y (Bewegung auf der Y Achse)
helper.points[i].z-=q.z; // Subtrahiere q.z Einheiten von helper.points[i].z (Bewegung auf der Z Achse)
tx=helper.points[i].x; // setze temporäre X Variable gleich auf die Helper's X Variable
ty=helper.points[i].y; // setze temporäre Y Variable gleich auf die Helper's Y Variable
tz=helper.points[i].z; // setze temporäre Z Variable gleich auf die Helper's Z Variable
glColor3f(0,1,1); // Setze Farbe auf hellen Blauton glVertex3f(tx,ty,tz); // Zeichne einen Punkt an der Stelle der aktuellen Temp Werte (Vertex) glColor3f(0,0.5f,1); // verdunkle die Farbe ein wenig tx-=2*q.x; ty-=2*q.y; ty-=2*q.y; // berechne zwei Positionen vorraus glVertex3f(tx,ty,tz); // zeichne einen zweiten Punkt an der neu berechneten Position glColor3f(0,0,1); // Setze Farbe auf ein sehr dunkles Blau tx-=2*q.x; ty-=2*q.y; ty-=2*q.y; // Berechne zwei Positionen vorraus glVertex3f(tx,ty,tz); // zeichne einen dritte Punkt an der zweiten neuen Position } // Dies erzeugt einen gespenstischen Schweif, während die Punkte sich bewegen glEnd(); // fertig mit dem Zeichnen der Punkte
// Wenn wir morphen und wir noch nicht durch alle 200 Schritte durch sind, inkrementieren wir unseren Step-Zähler
// Ansonsten setze Morphing auf False, Setze Source=Destination und setze den Step-Zähler zurück auf null.
if(morph && step<=steps)step++; else { morph=FALSE; sour=dest; step=0;}
}
GLvoid KillGLWindow(GLvoid) // Entferne das Fenster korrekt
{
objfree(&morph1); // springe zum Code um den für morph1 alloziierten Speicher freizugeben
objfree(&morph2); // springe zum Code um den für morph2 alloziierten Speicher freizugeben
objfree(&morph3); // springe zum Code um den für morph3 alloziierten Speicher freizugeben
objfree(&morph4); // springe zum Code um den für morph4 alloziierten Speicher freizugeben
objfree(&helper); // springe zum Code um den für helper alloziierten Speicher freizugeben
if (fullscreen) // Sind wir im Fullscreen Modus?
{
ChangeDisplaySettings(NULL,0); // Wenn ja, wechsle zurück zum Desktop
ShowCursor(TRUE); // Zeige den Maus-Zeiger
}
if (hRC) // Haben wir einen Rendering Context?
{
if (!wglMakeCurrent(NULL,NULL)) // Können wir den DC und RC Kontext freigeben?
{
MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}
if (!wglDeleteContext(hRC)) // Können wir den RC löschen?
{
MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
}
hRC=NULL; // Setze RC auf NULL
}
if (hDC && !ReleaseDC(hWnd,hDC)) // Können wir DC freigeben?
{
MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hDC=NULL; // Setze DC auf NULL
}
if (hWnd && !DestroyWindow(hWnd)) // Können wir das Fenster zerstören?
{
MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hWnd=NULL; // Setze hWnd auf NULL
}
if (!UnregisterClass("OpenGL",hInstance)) // Können wir die Klasse de-registrieren?
{
MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hInstance=NULL; // Setze hInstance auf NULL
}
}
BOOL CreateGLWindow() // erzeugt das GL Fenster LRESULT CALLBACK WndProc() // Handle für dieses Fenster
int WINAPI WinMain( HINSTANCE hInstance, // Instanz
HINSTANCE hPrevInstance, // vorherige Instanz
LPSTR lpCmdLine, // Kommandozeilen Parameter
int nCmdShow) // Fenster Anzeige Status
{
MSG msg; // Windows Nachrichten Struktur
BOOL done=FALSE; // Bool Variable um die Schleife zu beenden
// Frage den Benutzer, in welchen Modus er starten will
if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
{
fullscreen=FALSE; // Fenster-Modus
}
// erzeuge unser OpenGL Fenster
if (!CreateGLWindow("Piotr Cieslak & NeHe's Morphing Points Tutorial",640,480,16,fullscreen))
{
return 0; // Beende, wenn Fenster nicht erzeugt wurde
}
while(!done) // Schleife die so lange läuft, bis done=TRUE
{
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) // Wartet eine Nachricht?
{
if (msg.message==WM_QUIT) // Haben wir eine Nachricht zum beenden erhalten?
{
done=TRUE; // Wenn ja done=TRUE
}
else // Wenn nicht, bearbeite die Fenster-Nachrichten
{
TranslateMessage(&msg); // Übersetze die Nachricht
DispatchMessage(&msg); // bearbeite die Nachricht
}
}
else // Wenn da keine Nachrichten sind
{
// Zeichne die Szene. Schau nach der ESC-Taste und Beendigungs-Nachrichten von DrawGLScene()
if (active && keys[VK_ESCAPE]) // Aktiv? Wurde ein Signal zum Beenden erhalten?
{
done=TRUE; // ESC oder DrawGLScene Signalisiert, dass Beendet werden soll
}
else // Es ist noch nicht Zeit zum beenden, zeichne Screen neu
{
DrawGLScene(); // Zeichne die Szene (zeichne nicht neu, wenn inaktiv, nur 1% CPU Benutzung)
SwapBuffers(hDC); // Swap Buffers (Double Buffering)
if(keys[VK_PRIOR]) // wurde 'Bild hoch' gedrückt? zspeed+=0.01f; // inkrementiere zspeed if(keys[VK_NEXT]) // wurde 'Bild runter' gedrückt? zspeed-=0.01f; // dekrementiere zspeed if(keys[VK_DOWN]) // wurde Pfeil runter gedrückt? xspeed+=0.01f; // inkrementiere xspeed if(keys[VK_UP]) // wurde Pfeil hoch gedrückt? xspeed-=0.01f; // dekrementiere xspeed if(keys[VK_RIGHT]) // wurde Pfeil rechts gedrückt? yspeed+=0.01f; // inkrementiere yspeed if(keys[VK_LEFT]) // wurde Pfeil links gedrückt? yspeed-=0.01f; // dekrementiere yspeed
if (keys['Q']) // wurde die Q Taste gedrückt? cz-=0.01f; // bewege das Objekt vom Betrachter weg if (keys['Z']) // wurde die Z Taste gedrückt? cz+=0.01f; // bewege das Objekt zum Betrachter hin if (keys['W']) // wurde die W Taste gedrückt? cy+=0.01f; // bewege das Objekt nach oben? if (keys['S']) // wurde die S Taste gedrückt? cy-=0.01f; // bewege das Objekt nach unten if (keys['D']) // wurde die D Taste gedrückt? cx+=0.01f; // bewege das Objekt nach rechts if (keys['A']) // wurd die Taste A gedrückt? cx-=0.01f; // bewege das Objekt nach links
if (keys['1'] && (key!=1) && !morph) // wurde 1 gedrückt, key ungleich 1 und morph gleich False?
{
key=1; // Setze key auf 1 (um zu verhindern, dass 1 zweimal hinter einander gedrückt wird)
morph=TRUE; // Setze morph auf True (Startet den Morphing Prozess)
dest=&morph1; // Ziel Objekt in das gemorpht werden soll, dass dann morph1 wird
}
if (keys['2'] && (key!=2) && !morph) // wurde 2 gedrückt, key ungleich 2 und morph gleich False?
{
key=2; // Setze key auf 2 (um zu verhindern, dass 2 zweimal hinter einander gedrückt wird)
morph=TRUE; // Setze morph auf True (Startet den Morphing Prozess)
dest=&morph2; // Ziel Objekt in das gemorpht werden soll, dass dann morph2 wird
}
if (keys['3'] && (key!=3) && !morph) // wurde 3 gedrückt, key ungleich 3 und morph gleich False?
{
key=3; // Setze key auf 3 (um zu verhindern, dass 3 zweimal hinter einander gedrückt wird)
morph=TRUE; // Setze morph auf True (Startet den Morphing Prozess)
dest=&morph3; // Ziel Objekt in das gemorpht werden soll, dass dann morph3 wird
}
if (keys['4'] && (key!=4) && !morph) // wurde 4 gedrückt, key ungleich 4 und morph gleich False?
{
key=4; // Setze key auf 4 (um zu verhindern, dass 4 zweimal hinter einander gedrückt wird)
morph=TRUE; // Setze morph auf True (Startet den Morphing Prozess)
dest=&morph4; // Ziel Objekt in das gemorpht werden soll, dass dann morph4 wird
}
if (keys[VK_F1]) // Wurde F1 gedrückt?
{
keys[VK_F1]=FALSE; // Wenn ja, setze Taste auf FALSE
KillGLWindow(); // Kill unser aktuelles Fenster
fullscreen=!fullscreen; // Wechsel zwischen Fullscreen und Fester-Modus
// Erzeuge unser OpenGL Fenster erneut
if (!CreateGLWindow("Piotr Cieslak & NeHe's Morphing Points Tutorial",640,480,16,fullscreen))
{
return 0; // Beenden, wenn das Fenster nicht erzeugt wurde
}
}
}
}
}
// Shutdown
KillGLWindow(); // Kill das Fenster
return (msg.wParam); // Beende das Programm
}